package ee.az.test.lighting;


/**
 * @author Artur Zinatullin (artur.zinatullin@gmail.com) 2010
 */
public abstract class AbstractMap<S extends Spot> implements Map<S> {
	private final int width;
	private final int height;
	private final S map[][]; // array of rows, every row is array of Spot
	
	@SuppressWarnings("unchecked")
	public AbstractMap(int width, int height) {
		this.width = width;
		this.height = height;
		this.map = (S[][])new Spot[this.height][this.width];
	}
	
	/**
	 * Return default spot to be returned by getValue if value has not being set yet
	 */
	protected abstract S createDefaultSpot();
	
	public Line getLine(int startX, int startY, int endX, int endY) {
		int distance = Math.max(Math.abs(endX-startX), Math.abs(endY-startY));
		double stepX;
		double stepY;
		if (distance != 0) {
			stepX = ((double)(endX-startX))/distance;
			stepY = ((double)(endY-startY))/distance;
		} else {
			stepX = 1;
			stepY = 1;
		}
		
		Line result = new Line(distance+1);
		for (int i=0; i <= distance; i++) {
			int x = (int) (startX + 0.5 + i*stepX);
			int y = (int) (startY + 0.5 + i*stepY);
			result.getPoints()[i] = result.new Point(x, y, getValue(x, y));
		}
		return result;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (int y=0; y<getHeight(); y++) {
			sb.append("[");
			for (int x=0; x<getWidth(); x++) {
				sb.append(getValue(x, y) != null ? getValue(x, y) : " ");
			}
			sb.append("]\n");
		}
		return sb.toString();
	}
	
	/* (non-Javadoc)
	 * @see ee.az.test.lighting.Map#getValue(int, int)
	 */
	public S getValue(int x, int y) {
		if (map[y][x] == null) {
			map[y][x] = createDefaultSpot();
		}
		return map[y][x];
	}
	
	public S getValueBorderSafe(int x, int y) {
		if (x < 0 || x >= getWidth()) {
			return null;
		} else if (y < 0 || y >= getHeight()) {
			return null;
		} else {
			return getValue(x, y);
		}
	}

	public S getNeighbour(int x, int y, int quadrant) {
		switch(quadrant) {
		case 1: return getValueBorderSafe(x-1, y+1);
		case 2: return getValueBorderSafe(x, y+1);
		case 3: return getValueBorderSafe(x+1, y+1);
		case 4: return getValueBorderSafe(x-1, y);
		case 5: return getValueBorderSafe(x, y);
		case 6: return getValueBorderSafe(x+1, y);
		case 7: return getValueBorderSafe(x-1, y-1);
		case 8: return getValueBorderSafe(x, y-1);
		case 9: return getValueBorderSafe(x+1, y-1);
		default: return null;
		}
	}
	
	/* (non-Javadoc)
	 * @see ee.az.test.lighting.Map#setValue(int, int, S)
	 */
	public S setValue(int x, int y, S value) {
		S oldValue = getValue(x, y);
		map[y][x] = value;
		return oldValue;
	}
	
	/* (non-Javadoc)
	 * @see ee.az.test.lighting.Map#getWidth()
	 */
	public int getWidth() {
		return width;
	}
	/* (non-Javadoc)
	 * @see ee.az.test.lighting.Map#getHeight()
	 */
	public int getHeight() {
		return height;
	}
}
